grammar SFig;
options {
    output=AST;
    ASTLabelType=CommonTree;
}
tokens {
    BEAN_DEF;
    BEAN_REF;
    NEW_BEAN;
    SET_ATTRB;
    SET_PROP;
    USE_CTOR;
    NEW_PROPERTIES;
    NEW_LIST;
    STR_VAL;
    ESC_STR_VAL;
    ARRAY_LIST;
    NEW_STR;
}
@lexer::header {
package org.vossnet.sfig;
}

@header {
package org.vossnet.sfig;

import java.util.Properties;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.regex.*;
import org.antlr.runtime.tree.CommonTree;
import org.apache.log4j.Logger;
import static java.lang.String.format;

import static org.vossnet.sfig.RunFig.*;
}

@members {
private final Logger log = Logger.getLogger(getClass());
protected final Properties properties = new Properties();
protected final List<CommonTree> instances  = new ArrayList<CommonTree>();
protected final List<String> orderedBeanDefs = new ArrayList<String>();
protected final Map<String,BeanDef> beanDefs = new HashMap<String,BeanDef>();
}

file returns [  Properties includeProperties, List<CommonTree> preMadeBeanDefs, Map<String,Object> preMadeBeansMap,
                List<String> orderedBeanDefs, Map<String,BeanDef> beanDefsMap ]
    :   propInc* object*
        {
            $includeProperties = this.properties;
            $preMadeBeanDefs = this.instances;
            $preMadeBeansMap = new HashMap<String,Object>();
            $orderedBeanDefs = this.orderedBeanDefs;
            $beanDefsMap = this.beanDefs;
        }
        -> object*
    ;
    
propInc
    :   'properties_include' s=STRING ';'
        {
            includeProperties($s.text,properties);
            log.debug(format("\%nIncluded properties:\%n\%s\%n",properties));
        }
    ;

object
@init {
    int coll = 0;
}
    :   s=stringDecl
        {beanDefs.put($s.name,new BeanDef($s.name,$s.tree)); instances.add($s.tree); orderedBeanDefs.add($s.name);}
    |   t=textDecl
        {beanDefs.put($t.name,new BeanDef($t.name,$t.tree)); instances.add($t.tree); orderedBeanDefs.add($t.name);}
    |   p=properties
        {beanDefs.put($p.name,new BeanDef($p.name,$p.tree)); instances.add($p.tree); orderedBeanDefs.add($p.name);}
    |   l=arrayList
        {beanDefs.put($l.name,new BeanDef($l.name,$l.tree)); instances.add($l.tree); orderedBeanDefs.add($l.name);}
    |   b=bean {beanDefs.put($b.name,new BeanDef($b.name,$b.tree)); orderedBeanDefs.add($b.name);}
    ;

stringDecl returns [String name]
    :   'STRING' v=ID '=' stringType ';' {$name = $v.text;}
        -> ^(BEAN_DEF ^(NEW_STR $v stringType))
    ;

stringType returns [String text]
    :   es=escString {$text = $es.text;} 
    |   s=string     {$text = $s.text;}
    ;

textDecl returns [String name]
    :   'TEXT' v=ID '=' s=MLTEXT ';' {$name = $v.text;}
        -> ^(BEAN_DEF ^(NEW_STR $v ^(STR_VAL $s)))
    ;

arrayList returns [String name]
    :   l=LIST v=ID? listBody {$name=$v.text;}
        -> ^(BEAN_DEF ^(NEW_LIST $v $l listBody))
    ;
    
listBody : '{'! attrb* ctor? elems?  '}'! ;

elems : list ';'! ;

properties returns [String name]
    :   p=PROPERTIES v=ID? propsBody {$name=$v.text;}
        -> ^(BEAN_DEF ^(NEW_PROPERTIES $v $p propsBody))
    ;

propsBody
    :   '{'! attrb* ctor? propSet* '}'!
    ;
    
propSet
    :   propNm '=' expr ';' -> ^(SET_PROP propNm expr)
    ;

propNm : ID|string ;

bean returns [String name]
    :   clsType ID beanDef {$name=$ID.text;} -> ^(BEAN_DEF ^(NEW_BEAN ID clsType beanDef))
    ;

beanDef : '{'! attrb* ctor? assign*  '}'! ;
    
attrb
    :  '@' attrbNm '=' xexpr ';' -> ^(SET_ATTRB attrbNm xexpr)
    ;

attrbNm : ID|IDB ;

xexpr : ID|expr ;

ctor
    :   'this' '(' expr (',' expr)* ')' ';' -> ^(USE_CTOR ^(ARRAY_LIST expr*))
    ;
  
assign
    :   ID '=' expr ';' -> ^(SET_PROP ID expr)
    ;
    
expr
    :   escString
    |   string
    |   INT
    |   FLT
    |   CHR
    |   BOOL
    |   '$' ID -> ^(BEAN_REF ID)
    |   list
    ;

list
    :   '[' expr (',' expr)*  ']' -> ^(ARRAY_LIST expr*)
    ;

clsType : QID ;


escString returns [String text]
    :   '@' s=STRING {$text=$s.text;} -> ^(ESC_STR_VAL $s)
    ;

string returns [String text]
    :   s=STRING {$text=$s.text;} -> ^(STR_VAL $s)
    ;

STRING
    :   '"' .* '"'
        {setText(getText().substring(1, getText().length()-1));}
    ;

PROPERTIES : 'PROPERTIES' {setText("java.util.Properties");} ;
LIST : 'LIST' {setText("java.util.ArrayList");} ;

INT :   '-'?'0'..'9'+ ;
FLT :   '-'?'0'..'9'*'.''0'..'9'* ;
CHR :   '\'' . '\'' ;
BOOL :  'true'|'false' ;
ID  :   NAME ;
IDB :   ('_'|'a'..'z'|'A'..'Z') ('_'|'-'|'a'..'z'|'A'..'Z'|'0'..'9')* ;
WS  :   (' '|'\r'|'\n'|'\t')+ {$channel=HIDDEN;} ;
CMT :   '/*' .* '*/'     {$channel=HIDDEN;} ;
QID :   NAME ('.' NAME)* ;
fragment
NAME : ('_'|'a'..'z'|'A'..'Z') ('_'|'a'..'z'|'A'..'Z'|'0'..'9')* ;

MLTEXT
    :   '///' NEWLINE ( options {greedy=false;} : . )* '///'
        {setText(StringUtil.extractText(getText()));}
    ;

fragment
NEWLINE : '\r'? '\n' ;
